//===--- DataflowDiagnostics.cpp - Emits diagnostics based on PIL analysis ===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/ast/AstContext.h"
#include "polarphp/ast/DiagnosticEngine.h"
#include "polarphp/ast/DiagnosticsPIL.h"
#include "polarphp/ast/Expr.h"
#include "polarphp/ast/Stmt.h"
#include "polarphp/pil/lang/InstructionUtils.h"
#include "polarphp/pil/lang/PILConstants.h"
#include "polarphp/pil/lang/PILFunction.h"
#include "polarphp/pil/lang/PILInstruction.h"
#include "polarphp/pil/lang/PILLocation.h"
#include "polarphp/pil/lang/PILModule.h"
#include "polarphp/pil/lang/PILVisitor.h"
#include "polarphp/pil/optimizer/passmgr/Transforms.h"
#include "polarphp/pil/optimizer/utils/ConstExpr.h"

using namespace polar;

template<typename...T, typename...U>
static void diagnose(AstContext &Context, SourceLoc loc, Diag<T...> diag,
                     U &&...args) {
   Context.Diags.diagnose(loc,
                          diag, std::forward<U>(args)...);
}

static void diagnoseMissingReturn(const UnreachableInst *UI,
                                  AstContext &Context) {
   const PILBasicBlock *BB = UI->getParent();
   const PILFunction *F = BB->getParent();
   PILLocation FLoc = F->getLocation();

   Type ResTy;
   BraceStmt *BS;

   if (auto *FD = FLoc.getAsAstNode<FuncDecl>()) {
      ResTy = FD->getResultInterfaceType();
      BS = FD->getBody(/*canSynthesize=*/false);
   } else if (auto *CD = FLoc.getAsAstNode<ConstructorDecl>()) {
      ResTy = CD->getResultInterfaceType();
      BS = FD->getBody();
   } else if (auto *CE = FLoc.getAsAstNode<ClosureExpr>()) {
      ResTy = CE->getResultType();
      BS = CE->getBody();
   } else {
      llvm_unreachable("unhandled case in MissingReturn");
   }

   PILLocation L = UI->getLoc();
   assert(L && ResTy);
   if (!BS->empty()) {
      auto element = BS->getLastElement();
      if (auto expr = element.dyn_cast<Expr *>()) {
         if (expr->getType()->isEqual(ResTy)) {
            Context.Diags.diagnose(
                  expr->getStartLoc(),
                  diag::missing_return_last_expr, ResTy,
                  FLoc.isAstNode<ClosureExpr>() ? 1 : 0)
               .fixItInsert(expr->getStartLoc(), "return ");
            return;
         }
      }
   }
   auto diagID = F->isNoReturnFunction() ? diag::missing_never_call
                                         : diag::missing_return;
   diagnose(Context,
            L.getEndSourceLoc(),
            diagID, ResTy,
            FLoc.isAstNode<ClosureExpr>() ? 1 : 0);
}

static void diagnoseUnreachable(const PILInstruction *I,
                                AstContext &Context) {
   if (auto *UI = dyn_cast<UnreachableInst>(I)) {
      PILLocation L = UI->getLoc();

      // Invalid location means that the instruction has been generated by PIL
      // passes, such as DCE. FIXME: we might want to just introduce a separate
      // instruction kind, instead of keeping this invariant.
      //
      // We also do not want to emit diagnostics for code that was
      // transparently inlined. We should have already emitted these
      // diagnostics when we process the callee function prior to
      // inlining it.
      if (!L || L.is<MandatoryInlinedLocation>())
         return;

      // The most common case of getting an unreachable instruction is a
      // missing return statement. In this case, we know that the instruction
      // location will be the enclosing function.
      if (L.isAstNode<AbstractFunctionDecl>() || L.isAstNode<ClosureExpr>()) {
         diagnoseMissingReturn(UI, Context);
         return;
      }

      if (auto *Guard = L.getAsAstNode<GuardStmt>()) {
         diagnose(Context, Guard->getBody()->getEndLoc(),
                  diag::guard_body_must_not_fallthrough);
         return;
      }
   }
}

/// Issue diagnostics whenever we see Builtin.static_report(1, ...).
static void diagnoseStaticReports(const PILInstruction *I,
                                  PILModule &M) {

   // Find out if we are dealing with Builtin.staticReport().
   if (auto *BI = dyn_cast<BuiltinInst>(I)) {
      const BuiltinInfo &B = BI->getBuiltinInfo();
      if (B.ID == BuiltinValueKind::StaticReport) {

         // Report diagnostic if the first argument has been folded to '1'.
         OperandValueArrayRef Args = BI->getArguments();
         auto *V = dyn_cast<IntegerLiteralInst>(Args[0]);
         if (!V || V->getValue() != 1)
            return;

         diagnose(M.getAstContext(), I->getLoc().getSourceLoc(),
                  diag::static_report_error);
      }
   }
}

/// Emit a diagnostic for `poundAssert` builtins whose condition is
/// false or whose condition cannot be evaluated.
static void diagnosePoundAssert(const PILInstruction *I,
                                PILModule &M,
                                ConstExprEvaluator &constantEvaluator) {
   auto *builtinInst = dyn_cast<BuiltinInst>(I);
   if (!builtinInst ||
       builtinInst->getBuiltinKind() != BuiltinValueKind::PoundAssert)
      return;

   SmallVector<SymbolicValue, 1> values;
   constantEvaluator.computeConstantValues({builtinInst->getArguments()[0]},
                                           values);
   SymbolicValue value = values[0];
   if (!value.isConstant()) {
      diagnose(M.getAstContext(), I->getLoc().getSourceLoc(),
               diag::pound_assert_condition_not_constant);

      // If we have more specific information about what went wrong, emit
      // notes.
      if (value.getKind() == SymbolicValue::Unknown)
         value.emitUnknownDiagnosticNotes(builtinInst->getLoc());
      return;
   }
   assert(value.getKind() == SymbolicValue::Integer &&
          "sema prevents non-integer #assert condition");

   APInt intValue = value.getIntegerValue();
   assert(intValue.getBitWidth() == 1 &&
          "sema prevents non-int1 #assert condition");
   if (intValue.isNullValue()) {
      auto *message = cast<StringLiteralInst>(builtinInst->getArguments()[1]);
      StringRef messageValue = message->getValue();
      if (messageValue.empty())
         messageValue = "assertion failed";
      diagnose(M.getAstContext(), I->getLoc().getSourceLoc(),
               diag::pound_assert_failure, messageValue);
      return;
   }
}

static void diagnoseUnspecializedPolymorphicBuiltins(PILInstruction *inst) {
   // We only validate if we are in a non-transparent function.
   if (inst->getFunction()->isTransparent())
      return;

   auto *bi = dyn_cast<BuiltinInst>(inst);
   if (!bi)
      return;

   auto kind = bi->getBuiltinKind();
   if (!kind)
      return;

   if (!isPolymorphicBuiltin(*kind))
      return;

   const auto &builtinInfo = bi->getBuiltinInfo();

   // First that the parameters were acceptable so we can emit a nice error to
   // guide the user.
   for (PILValue value : bi->getOperandValues()) {
      PILType type = value->getType();
      SourceLoc loc;
      if (auto *inst = value->getDefiningInstruction()) {
         loc = inst->getLoc().getSourceLoc();
      } else {
         loc = bi->getLoc().getSourceLoc();
      }

      if (!type.is<BuiltinType>() || !type.isTrivial(*bi->getFunction())) {
         diagnose(bi->getModule().getAstContext(), loc,
                  diag::polymorphic_builtin_passed_non_trivial_non_builtin_type,
                  type.getAstType());
         return;
      }
   }

   // Ok, we have a valid type for a polymorphic builtin. Make sure we actually
   // have a static overload for this type.
   PolymorphicBuiltinSpecializedOverloadInfo overloadInfo;
   bool ableToMapToStaticOverload = overloadInfo.init(bi);
   (void)ableToMapToStaticOverload;
   assert(ableToMapToStaticOverload);
   if (!overloadInfo.doesOverloadExist()) {
      diagnose(bi->getModule().getAstContext(), bi->getLoc().getSourceLoc(),
               diag::polymorphic_builtin_passed_type_without_static_overload,
               overloadInfo.staticOverloadIdentifier,
               getBuiltinName(builtinInfo.ID),
               overloadInfo.argTypes.front().getAstType());
      return;
   }

   // Otherwise, something happen that we did not understand. This can only
   // happen if we specialize the generic type in the builtin /after/ constant
   // propagation runs at -Onone but before dataflow diagnostics. This is an
   // error in implementation, so we assert.
   llvm_unreachable("Found generic builtin with known static overload that it "
                    "could be transformed to. Did this builtin get its generic "
                    "type specialized /after/ constant propagation?");
}

namespace {
class EmitDFDiagnostics : public PILFunctionTransform {
   ~EmitDFDiagnostics() override {}

   /// The entry point to the transformation.
   void run() override {
      // Don't rerun diagnostics on deserialized functions.
      if (getFunction()->wasDeserializedCanonical())
         return;

      PILModule &M = getFunction()->getModule();
      for (auto &BB : *getFunction()) {
         for (auto &I : BB) {
            diagnoseUnreachable(&I, M.getAstContext());
            diagnoseStaticReports(&I, M);
            diagnoseUnspecializedPolymorphicBuiltins(&I);
         }
      }

      if (M.getAstContext().LangOpts.EnableExperimentalStaticAssert) {
         SymbolicValueBumpAllocator allocator;
         ConstExprEvaluator constantEvaluator(allocator,
                                              getOptions().AssertConfig);
         for (auto &BB : *getFunction())
            for (auto &I : BB)
               diagnosePoundAssert(&I, M, constantEvaluator);
      }
   }
};

} // end anonymous namespace

PILTransform *polar::createEmitDFDiagnostics() {
   return new EmitDFDiagnostics();
}
